PVI is a soil-line–based vegetation index that measures the perpendicular distance
between a pixel and the soil line in Red–NIR space, improving separation between
bare soil and green vegetation under varying soil brightness.
1. Scientific Definition
The Perpendicular Vegetation Index (PVI) is a spectral index that uses
the geometric distance of a pixel from the soil line in the Red–Near-InfraRed (NIR)
reflectance space. The soil line describes the linear relationship between Red and NIR
reflectance for bare soils:
NIR = a × Red + b
PVI quantifies how far a pixel lies above this soil line. Higher PVI values indicate
stronger vegetation signal (higher NIR and lower Red compared to soil), while values
near zero correspond to bare soil or very sparse vegetation.
Formula
A common formulation of PVI is:
PVI = (NIR − a × Red − b) / √(1 + a²)
Dimensionless (relative distance)
where:
NIR = Near-InfraRed reflectance
Red = Red reflectance
a = slope of the soil line
b = intercept of the soil line
Typical Interpretation (qualitative)
PVI (relative)
Interpretation
≈ 0
Bare soil, exposed ground, or very sparse vegetation close to the soil line
Low positive
Low–moderate vegetation cover, grassland or mixed soil–vegetation pixels
High positive
Dense and healthy vegetation with strong NIR and low Red reflectance
Negative
Bright non-vegetated surfaces or atypical spectra below the soil line
Key Applications
Separating vegetation from soil in areas with strong soil background effects
Vegetation mapping in semi-arid and arid regions
Complementing NDVI/SAVI when soil brightness varies strongly
Input feature in land cover classification and agricultural monitoring
2. Data & Bands for PVI
Common Sensors & Bands
Sentinel-2 (ESA) – 10 m
Red: B4 (~665 nm)
NIR: B8 (~842 nm)
Landsat 8/9 OLI – 30 m
Red: B4
NIR: B5
Soil Line Parameters
In practice, a and b should be estimated from bare soil
pixels in your scene by fitting a linear regression in Red–NIR space. For demonstration
purposes, many examples use approximate values such as:
a ≈ 1.0
b ≈ 0.0 – 0.05 (depending on sensor and scene)
Good Practice
Use atmospherically corrected surface reflectance products (e.g. COPERNICUS/S2_SR).
Filter scenes by cloud percentage and apply cloud masking.
Estimate the soil line using bare soil samples for more accurate PVI.
Clip the final PVI raster to your AOI before exporting.
Palette Suggestion
A simple PVI palette similar to vegetation indices:
[ "#440154", "#3b528b", "#21908c", "#5dc963", "#fde725" ]
3. Google Earth Engine Code – PVI for Any AOI
Steps: open code.earthengine.google.com → New Script → paste the code →
draw your AOI as geometry on the map → click Run.
Then export PVI as GeoTIFF to Google Drive.
// PVI for any Area of Interest (AOI) using Sentinel-2 SR
// -------------------------------------------------------
// 1) Go to: https://code.earthengine.google.com
// 2) Click "New Script" and paste this code.
// 3) On the map: draw your AOI (Polygon/Rectangle).
// It will appear as a variable named 'geometry' in the left panel.
// 4) Click "Run" to display PVI.
// 5) In the Tasks tab, click "Run" to export PVI to Google Drive.
// -------------------------------------------------------
// 1. Define Area of Interest (AOI)
// -------------------------------------------------------
var roi = geometry; // Make sure a 'geometry' object exists in the left panel
// Center the map on the AOI
Map.centerObject(roi, 11);
// -------------------------------------------------------
// 2. Define time range
// -------------------------------------------------------
var startDate = '2023-01-01';
var endDate = '2023-12-31';
// -------------------------------------------------------
// 3. Load Sentinel-2 Surface Reflectance collection
// -------------------------------------------------------
var s2 = ee.ImageCollection('COPERNICUS/S2_SR')
.filterBounds(roi)
.filterDate(startDate, endDate)
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20));
// Create a median composite and clip to AOI
var image = s2.median().clip(roi);
// -------------------------------------------------------
// 4. Define soil line parameters (example values)
// Ideally, estimate 'a' and 'b' from bare soil samples
// -------------------------------------------------------
var a = 1.0; // soil line slope
var b = 0.05; // soil line intercept
// -------------------------------------------------------
// 5. Compute PVI
// PVI = (NIR - a * Red - b) / sqrt(1 + a^2)
// -------------------------------------------------------
var pvi = image.expression(
'(NIR - a * RED - b) / sqrt(1 + a * a)',
{
'NIR': image.select('B8'), // NIR band (Sentinel-2)
'RED': image.select('B4'), // Red band
'a': a,
'b': b
}
).rename('PVI');
// -------------------------------------------------------
// 6. Visualization on the map
// -------------------------------------------------------
var pviVis = {
min: -0.5,
max: 0.8,
palette: [
'#440154', // low / near soil / negative
'#3b528b',
'#21908c',
'#5dc963',
'#fde725' // high vegetation signal
]
};
// Add PVI layer to the map
Map.addLayer(pvi, pviVis, 'PVI (Sentinel-2)', true);
// Optionally, also show a true color composite for context
var s2_rgb = ee.ImageCollection('COPERNICUS/S2_SR')
.filterBounds(roi)
.filterDate(startDate, endDate)
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20))
.select(['B4','B3','B2']) // RGB
.median()
.clip(roi);
Map.addLayer(s2_rgb, {min:0, max:3000}, 'True Color (RGB)', false);
// -------------------------------------------------------
// 7. Export PVI as GeoTIFF to Google Drive
// -------------------------------------------------------
Export.image.toDrive({
image: pvi,
description: 'PVI_Export',
fileNamePrefix: 'PVI_Export',
region: roi,
scale: 10, // Sentinel-2 native resolution for B8/B4
crs: 'EPSG:4326',
maxPixels: 1e13
});
// End of script